﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Drawing;
using System.IO;

namespace Vit.Report.XmlWebReport
{
    public class TableElement : CellElement
    {
        private List<TableRowElement> rows;
        private List<TableHeaderElement> headers;

        public TableElement(Page page, string topLeftPosition, string bottomRightPosition, string content)
            : base(page, topLeftPosition, bottomRightPosition, content)
        {
            this.headers = new List<TableHeaderElement>();
            this.rows = new List<TableRowElement>();
            this.FixRows = -1;
            PositionType = PositionType.RuntimeCalculationSource;
            CopyHeaderWhenSplit = false;
            this.HasBorders = true;
        }

        public TableElement(Page page, RectanglePosition rect, string content)
            : base(page, rect, content)
        {
            this.headers = new List<TableHeaderElement>();
            this.rows = new List<TableRowElement>();
            this.FixRows = -1;
            PositionType = PositionType.RuntimeCalculationSource;
            CopyHeaderWhenSplit = false;
            this.HasBorders = true;
        }

        protected override void Initialize()
        {
            base.Initialize();
            this.CssClass += " table";
        }

        public override void CalculatePosition()
        {
            if (IsPrintable == false)
                return;

            base.CalculatePosition();

            if (this.PositionType != PositionType.Fix && (page.XOffset > 0 || page.YOffset > 0))
                this.Move(page.XOffset, page.YOffset);

            RectanglePosition last = null;

            foreach (TableHeaderElement header in Headers)
            {
                if (last != null && header.PositionType != PositionType.Fix && (last.BottomIndex > header.RectanglePosition.BottomIndex))
                    header.RectanglePosition.Move(0, last.BottomIndex - header.RectanglePosition.BottomIndex);
                header.CalculatePosition();
                last = header.RectanglePosition.Clone();
                if (this.HasBorders && header.CssClass.IndexOf("border") < 0)
                {
                    header.CssClass += header.HeaderIndex == 1 ? " border-all" : " border-tbr";
                }
            }
            int rowIndex = 0;
            foreach (TableRowElement row in Rows)
            {
                if (last != null && (last.BottomIndex >= row.RectanglePosition.TopIndex))
                {
                    row.RectanglePosition.Move(0, last.BottomIndex - row.RectanglePosition.TopIndex + 1);
                    row.Cells.ForEach(o => o.RectanglePosition.Move(0, last.BottomIndex - o.RectanglePosition.TopIndex + 1));
                }
                row.CalculatePosition();
                last = row.RectanglePosition.Clone();
                if (last.BottomIndex > page.ContentRectanglePosition.BottomIndex)
                    break;
                if (this.CellFont != null)
                    row.Cells.ForEach(o => o.Font = this.CellFont);

                foreach (TableCellElement cell in row.Cells)
                {
                    if (this.HasBorders && cell.CssClass.IndexOf("border") < 0)
                    {
                        cell.CssClass += cell.CellIndex == 1 ? " border-lbr" : " border-br";
                    }

                    if (!string.IsNullOrEmpty(CellCssClass))
                        cell.CssClass += " " + CellCssClass;
                    if (!string.IsNullOrEmpty(CellCssStyle))
                        cell.CssStyle += " " + CellCssStyle;
                }
                rowIndex++;
            }

            if (rowIndex < this.Rows.Count)
            {
                EnsureExtraYTable();
                int lastRowTop = extraYTable.RectanglePosition.TopIndex;
                int headerSpan = 0;
                if (CopyHeaderWhenSplit)
                {
                    foreach (var header in this.Headers)
                    {
                        int headerTop = lastRowTop + header.RectanglePosition.TopIndex - this.RectanglePosition.TopIndex;
                        TableHeaderElement th = new TableHeaderElement(extraYTable,
                            new RectanglePosition(
                                string.Format("{0}{1}", header.RectanglePosition.LeftIndex, headerTop),
                                string.Format("{0}{1}", header.RectanglePosition.RightIndex, headerTop + header.RectanglePosition.YSpan - 1)),
                                header.ColumnName, header.Content);

                        if (th.RectanglePosition.YSpan > headerSpan)
                            headerSpan = th.RectanglePosition.YSpan;

                        th.RectanglePosition.OffsetPoint = header.RectanglePosition.OffsetPoint;
                        th.RectanglePosition.AppendSize = header.RectanglePosition.AppendSize;

                        th.CellCssClass = header.CellCssClass;
                        th.CellCssStyle = header.CellCssStyle;
                        th.CellFont = header.CellFont;
                        th.CssClass = header.CssClass;
                        th.CssStyle = header.CssStyle;
                        th.Editable = header.Editable;
                        th.Font = header.Font;
                        th.Format = header.Format;
                        th.Index = header.Index;
                        th.IsFooter = header.IsFooter;
                        th.IsHeader = header.IsHeader;
                        th.IsHtml = header.IsHtml;
                        th.IsPrintable = header.IsPrintable;
                        th.IsVirtual = header.IsVirtual;
                        th.LetterSpacing = header.LetterSpacing;
                        th.LineHeight = header.LineHeight;
                        th.NoWrap = header.NoWrap;
                        th.PositionType = header.PositionType;
                    }
                }
                else
                {
                    CreateTopLineElement(extraYTable, lastRowTop);
                }

                lastRowTop += headerSpan;

                for (int i = rowIndex; i < this.Rows.Count; i++)
                {
                    TableRowElement row = this.Rows[i];
                    TableRowElement tr = new TableRowElement(extraYTable,
                        new RectanglePosition(
                            string.Format("{0}{1}", row.RectanglePosition.LeftIndex, lastRowTop),
                            string.Format("{0}{1}", row.RectanglePosition.RightIndex, lastRowTop + row.RectanglePosition.YSpan)),
                            row.Content);

                    tr.CssClass = row.CssClass;
                    tr.CssStyle = row.CssStyle;
                    tr.Editable = row.Editable;
                    tr.Font = row.Font;
                    tr.Format = row.Format;
                    tr.RowIndex = row.RowIndex;
                    tr.IsFooter = row.IsFooter;
                    tr.IsHeader = row.IsHeader;
                    tr.IsHtml = row.IsHtml;
                    tr.IsPrintable = row.IsPrintable;
                    tr.LetterSpacing = row.LetterSpacing;
                    tr.LineHeight = row.LineHeight;
                    tr.NoWrap = row.NoWrap;
                    tr.PositionType = row.PositionType;

                    foreach (TableCellElement cell in row.Cells)
                    {
                        TableCellElement tc = new TableCellElement(tr,
                            new RectanglePosition(
                                string.Format("{0}{1}", cell.RectanglePosition.LeftIndex, tr.RectanglePosition.TopIndex),
                                string.Format("{0}{1}", cell.RectanglePosition.RightIndex, tr.RectanglePosition.BottomIndex)),
                                cell.Content);

                        tc.RectanglePosition.OffsetPoint = cell.RectanglePosition.OffsetPoint;
                        tc.RectanglePosition.AppendSize = cell.RectanglePosition.AppendSize;

                        tc.CellIndex = cell.CellIndex;
                        tc.CssClass = cell.CssClass;
                        tc.CssStyle = cell.CssStyle;
                        tc.Editable = cell.Editable;
                        tc.Font = cell.Font;
                        tc.Format = cell.Format;
                        tc.IsFooter = cell.IsFooter;
                        tc.IsHeader = cell.IsHeader;
                        tc.IsHtml = cell.IsHtml;
                        tc.IsPrintable = cell.IsPrintable;
                        tc.LetterSpacing = cell.LetterSpacing;
                        tc.LineHeight = cell.LineHeight;
                        tc.NoWrap = cell.NoWrap;
                        tc.PositionType = cell.PositionType;
                    }
                }

                this.Rows.RemoveRange(rowIndex, this.Rows.Count - rowIndex);
            }
            else
            {
                if (extraYTable != null)
                {
                    CreateTopLineElement(extraYTable, extraYTable.RectanglePosition.TopIndex);
                }
            }

            page.YOffset += rowIndex * RowSpan;
        }

        private void CreateTopLineElement(TableElement extraTable, int lastRowTop)
        {
            CellElement topLineCell = new CellElement(extraTable.Page,
            new RectanglePosition(
                string.Format("{0}{1}", extraTable.RectanglePosition.LeftIndex, lastRowTop),
                string.Format("{0}{1}", this.Rows[0].Cells.Last().RectanglePosition.RightIndex, lastRowTop)),
                "");
            topLineCell.ID = string.Format("{0}_topLine", extraTable.ID);
            topLineCell.CssClass += " border-t";
            topLineCell.PositionType = PositionType.Fix;
        }

        public void SetData(IEnumerable data, string mapName, Func<string, object, string, string> getColumnValueOfRow)
        {
            char maxRight = this.Headers.Max(o => o.RectanglePosition.RightIndex);
            int rowY = this.Headers.Max(o => o.RectanglePosition.BottomIndex) + 1;
            string rowTopLeft;
            string rowBottomRight;

            if (data != null)
            {
                foreach (object d in data)
                {
                    rowTopLeft = string.Format("{0}{1}", this.RectanglePosition.LeftIndex, rowY);
                    rowBottomRight = string.Format("{0}{1}", maxRight, rowY + RowSpan - 1);

                    TableRowElement row = new TableRowElement(this, rowTopLeft, rowBottomRight, "");

                    foreach (TableHeaderElement header in Headers)
                    {
                        if (header.IsVirtual)
                            continue;

                        string cellTopLeft = string.Format("{0}{1}", header.RectanglePosition.LeftIndex, rowY);
                        string cellBottomRight = string.Format("{0}{1}", header.RectanglePosition.RightIndex, rowY + RowSpan - 1);

                        string val = "";
                        switch (header.ColumnName)
                        {
                            case "{INDEX}":
                                val = row.RowIndex.ToString();
                                break;
                            default:
                                val = getColumnValueOfRow(mapName, d, header.ColumnName);
                                break;
                        }

                        TableCellElement cell = new TableCellElement(row, cellTopLeft, cellBottomRight, val);
                        cell.PositionType = this.PositionType;
                        //cell.Offset(header.RectanglePosition.OffsetPoint.X, header.RectanglePosition.OffsetPoint.Y);
                        //cell.Append(header.RectanglePosition.AppendSize.Width, header.RectanglePosition.AppendSize.Height);
                        cell.CssClass += header.CellCssClass;
                        cell.CssStyle += header.CellCssStyle;
                        if(header.CellFont != null)
                            cell.Font = header.CellFont;
                    }

                    if (FixRows > 0 && row.RowIndex >= FixRows)
                    {
                        //foreach (TableRowElement r1 in this.rows)
                        //{
                        //    foreach (TableCellElement c1 in r1.Cells)
                        //        c1.Content = " ";
                        //}

                        //TableCellElement emptyCell = new TableCellElement(this.rows[0], this.rows[0].RectanglePosition, MsgWhenRowsExceedFixRows);
                        //rowY += this.RowSpan;
                        break;
                    }
                    rowY += this.RowSpan;
                }
            }

            if (FixRows > 0 && this.Rows.Count < FixRows)
            {
                for (int i = this.Rows.Count; i < this.FixRows; i++)
                {
                    rowTopLeft = string.Format("{0}{1}", this.RectanglePosition.LeftIndex, rowY);
                    rowBottomRight = string.Format("{0}{1}", maxRight, rowY + RowSpan - 1);
                    TableRowElement row = new TableRowElement(this, rowTopLeft, rowBottomRight, "");
                    foreach (TableHeaderElement header in Headers)
                    {
                        if (header.IsVirtual)
                            continue;
                        string cellTopLeft = string.Format("{0}{1}", header.RectanglePosition.LeftIndex, rowY);
                        string cellBottomRight = string.Format("{0}{1}", header.RectanglePosition.RightIndex, rowY + RowSpan - 1);
                        TableCellElement cell = new TableCellElement(row, cellTopLeft, cellBottomRight, " ");
                        cell.PositionType = this.PositionType;
                        cell.Offset(header.RectanglePosition.OffsetPoint.X, header.RectanglePosition.OffsetPoint.Y);
                        cell.Append(header.RectanglePosition.AppendSize.Width, header.RectanglePosition.AppendSize.Height);
                        cell.CssClass += header.CellCssClass;
                        cell.CssStyle += header.CellCssStyle;
                        if (header.CellFont != null)
                            cell.Font = header.CellFont;
                    }
                    rowY += this.RowSpan;
                }
            }
        }

        public override void Move(int xOffset, int yOffset)
        {
            base.Move(xOffset, yOffset);

            foreach (TableHeaderElement header in Headers)
                header.Move(xOffset, yOffset);
            foreach (TableRowElement row in Rows)
                row.Move(xOffset, yOffset);
        }

        public override void Offset(float x, float y)
        {
            base.Offset(x, y);

            foreach (TableHeaderElement header in Headers)
                header.Offset(x, y);
            foreach (TableRowElement row in Rows)
                row.Offset(x, y);
        }

        public override void Render(TextWriter writer)
        {
            if (IsPrintable == false)
                return;

            foreach (TableHeaderElement header in Headers)
                header.Render(writer);

            foreach (TableRowElement row in Rows)
                row.Render(writer);
        }

        public void EnsureExtraYTable()
        {
            if (extraYTable == null)
            {
                page.EnsureExtraYPage();
                extraYTable = new TableElement(page.ExtraYPage, "A1", "A1", "");
                extraYTable.Move(0, ExtraYTable.Page.HeaderOffset);
                page.ExtraYPage.Elements.Remove(extraYTable);
                page.ExtraYPage.Elements.Insert(0, extraYTable);
                extraYTable.ID = this.ID + "_extraYTable";
                extraYTable.RowSpan = this.RowSpan;
                extraYTable.CellCssClass = this.CellCssClass;
                extraYTable.CellCssStyle = this.CellCssStyle;
                extraYTable.CellFont = this.CellFont;
                extraYTable.CssClass = this.CssClass;
                extraYTable.CssStyle = this.CssStyle;
                extraYTable.FixRows = this.FixRows;
                extraYTable.CopyHeaderWhenSplit = this.CopyHeaderWhenSplit;
            }
        }

        protected TableElement extraYTable;
        public TableElement ExtraYTable
        {
            get
            {
                return extraYTable;
            }
        }

        public List<TableHeaderElement> Headers { get { return this.headers; } }
        public List<TableRowElement> Rows { get { return this.rows; } }
        public int FixRows { get; set; }
        public string MsgWhenRowsExceedFixRows { get; set; }
        public bool HasBorders { get; set; }
        public string CellCssClass { get; set; }
        public string CellCssStyle { get; set; }
        public Font CellFont { get; set; }
        public int RowSpan { get; set; }
        public bool CopyHeaderWhenSplit { get; set; }
    }
}
